/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Test case hierarchy iterator.
 *//*--------------------------------------------------------------------*/

#include "tcuTestHierarchyIterator.hpp"
#include "tcuCommandLine.hpp"

namespace tcu
{

using std::string;
using std::vector;

// TestHierarchyInflater

TestHierarchyInflater::TestHierarchyInflater (void)
{
}

TestHierarchyInflater::~TestHierarchyInflater (void)
{
}

// DefaultHierarchyInflater

DefaultHierarchyInflater::DefaultHierarchyInflater (TestContext& testCtx)
	: m_testCtx(testCtx)
{
}

DefaultHierarchyInflater::~DefaultHierarchyInflater (void)
{
}

void DefaultHierarchyInflater::enterTestPackage (TestPackage* testPackage, vector<TestNode*>& children)
{
	{
		Archive* const	pkgArchive	= testPackage->getArchive();

		if (pkgArchive)
			m_testCtx.setCurrentArchive(*pkgArchive);
		else
			m_testCtx.setCurrentArchive(m_testCtx.getRootArchive());
	}

	testPackage->init();
	testPackage->getChildren(children);

	// write default session info if it was not done by package
	m_testCtx.writeSessionInfo();
}

void DefaultHierarchyInflater::leaveTestPackage (TestPackage* testPackage)
{
	m_testCtx.setCurrentArchive(m_testCtx.getRootArchive());
	testPackage->deinit();
}

void DefaultHierarchyInflater::enterGroupNode (TestCaseGroup* testGroup, vector<TestNode*>& children)
{
	testGroup->init();
	testGroup->getChildren(children);
}

void DefaultHierarchyInflater::leaveGroupNode (TestCaseGroup* testGroup)
{
	testGroup->deinit();
}

// TestHierarchyIterator

TestHierarchyIterator::TestHierarchyIterator (TestPackageRoot&			rootNode,
											  TestHierarchyInflater&	inflater,
											  const CaseListFilter&		caseListFilter)
	: m_inflater		(inflater)
	, m_caseListFilter	(caseListFilter)
	, m_groupNumber		(0)
{
	// Init traverse state and "seek" to first reportable node.
	NodeIter iter(&rootNode);
	iter.setState(NodeIter::NISTATE_ENTER); // Root is never reported
	m_sessionStack.push_back(iter);
	next();
}

TestHierarchyIterator::~TestHierarchyIterator (void)
{
	// Tear down inflated nodes in m_sessionStack
	for (vector<NodeIter>::reverse_iterator iter = m_sessionStack.rbegin(); iter != m_sessionStack.rend(); ++iter)
	{
		TestNode* const		node		= iter->node;
		const TestNodeType	nodeType	= node->getNodeType();

		switch (nodeType)
		{
			case NODETYPE_ROOT:		/* root is not de-initialized */								break;
			case NODETYPE_PACKAGE:	m_inflater.leaveTestPackage(static_cast<TestPackage*>(node));	break;
			case NODETYPE_GROUP:	m_inflater.leaveGroupNode(static_cast<TestCaseGroup*>(node));	break;
			default:
				break;
		}
	}
}

TestHierarchyIterator::State TestHierarchyIterator::getState (void) const
{
	if (!m_sessionStack.empty())
	{
		const NodeIter&	iter	= m_sessionStack.back();

		DE_ASSERT(iter.getState() == NodeIter::NISTATE_ENTER ||
				  iter.getState() == NodeIter::NISTATE_LEAVE);

		return iter.getState() == NodeIter::NISTATE_ENTER ? STATE_ENTER_NODE : STATE_LEAVE_NODE;
	}
	else
		return STATE_FINISHED;
}

TestNode* TestHierarchyIterator::getNode (void) const
{
	DE_ASSERT(getState() != STATE_FINISHED);
	return m_sessionStack.back().node;
}

const std::string& TestHierarchyIterator::getNodePath (void) const
{
	DE_ASSERT(getState() != STATE_FINISHED);
	return m_nodePath;
}

std::string TestHierarchyIterator::buildNodePath (const vector<NodeIter>& nodeStack)
{
	string nodePath;
	for (size_t ndx = 1; ndx < nodeStack.size(); ndx++)
	{
		const NodeIter& iter = nodeStack[ndx];
		if (ndx > 1) // ignore root package
			nodePath += ".";
		nodePath += iter.node->getName();
	}
	return nodePath;
}

void TestHierarchyIterator::next (void)
{
	while (!m_sessionStack.empty())
	{
		NodeIter&			iter		= m_sessionStack.back();
		TestNode* const		node		= iter.node;
		const bool			isLeaf		= isTestNodeTypeExecutable(node->getNodeType());

		switch (iter.getState())
		{
			case NodeIter::NISTATE_INIT:
			{
				const std::string nodePath = buildNodePath(m_sessionStack);

				// Return to parent if name or runner type doesn't match filter.
				if (!(isLeaf ? (m_caseListFilter.checkRunnerType(node->getRunnerType()) && m_caseListFilter.checkTestCaseName(nodePath.c_str()))
							 : m_caseListFilter.checkTestGroupName(nodePath.c_str())))
				{
					m_sessionStack.pop_back();
					break;
				}

				m_nodePath = nodePath;
				iter.setState(NodeIter::NISTATE_ENTER);
				return; // Yield enter event
			}

			case NodeIter::NISTATE_ENTER:
			{
				if (isLeaf)
				{
					iter.setState(NodeIter::NISTATE_LEAVE);
					return; // Yield leave event
				}
				else
				{
					iter.setState(NodeIter::NISTATE_TRAVERSE_CHILDREN);
					iter.children.clear();

					switch (node->getNodeType())
					{
						case NODETYPE_ROOT:		static_cast<TestPackageRoot*>(node)->getChildren(iter.children);				break;
						case NODETYPE_PACKAGE:	m_inflater.enterTestPackage(static_cast<TestPackage*>(node), iter.children);	break;
						case NODETYPE_GROUP:	m_inflater.enterGroupNode(static_cast<TestCaseGroup*>(node), iter.children);	break;
						default:
							DE_ASSERT(false);
					}
				}

				break;
			}

			case NodeIter::NISTATE_TRAVERSE_CHILDREN:
			{
				int numChildren = (int)iter.children.size();
				if (++iter.curChildNdx < numChildren)
				{
					// Push child to stack.
					TestNode* childNode = iter.children[iter.curChildNdx];

					// Check whether this is a bottom-level group (child is executable)
					// and whether that group should be filtered out.
					if ( isTestNodeTypeExecutable(childNode->getNodeType()) )
					{
						const std::string testName = m_nodePath + "." + childNode->getName();
						if(!m_caseListFilter.checkCaseFraction(m_groupNumber, testName))
							break;
					}
					m_sessionStack.push_back(NodeIter(childNode));
				}
				else
				{
					iter.setState(NodeIter::NISTATE_LEAVE);
					if (node->getNodeType() != NODETYPE_ROOT)
						return; // Yield leave event
				}

				break;
			}

			case NodeIter::NISTATE_LEAVE:
			{
				// Leave node.
				if (!isLeaf)
				{
					switch (node->getNodeType())
					{
						case NODETYPE_ROOT:		/* root is not de-initialized */								break;
						case NODETYPE_PACKAGE:	m_inflater.leaveTestPackage(static_cast<TestPackage*>(node));	break;
						case NODETYPE_GROUP:	m_inflater.leaveGroupNode(static_cast<TestCaseGroup*>(node));	break;
						default:
							DE_ASSERT(false);
					}
					m_groupNumber++;
				}

				m_sessionStack.pop_back();
				m_nodePath = buildNodePath(m_sessionStack);
				break;
			}

			default:
				DE_ASSERT(false);
				return;
		}
	}

	DE_ASSERT(m_sessionStack.empty() && getState() == STATE_FINISHED);
}

} // tcu
